/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny.filing;

import com.softwarecraftsmen.orogeny.filing.fileSystems.FileSystem;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import static java.lang.String.format;
import static java.util.Locale.UK;

// TODO: Folders need a FileSystem, not only to prevent isRoot names being used, but also to do case sensitive or insenstive comparisions of equality
public abstract class Folder
{
	private static final String CurrentFolderName = ".";
	private static final String ParentFolderName = "..";

	@NotNull
	public static Folder folder(final @NotNull String name)
	{
		if (name.equals(CurrentFolderName))
		{
			return Current;
		}
		if (name.equals(ParentFolderName))
		{
			return Parent;
		}
		return Ordinary(name);
	}

	@NotNull
	public static List<Folder> fromFile(final @NotNull FileSystem fileSystem, final @NotNull File directoryPath)
	{
		final Stack<Folder> folders = new Stack<>();
		File currentFolder = directoryPath;
		boolean isRoot = false;
		while (!isRoot)
		{
			isRoot = fileSystem.isRoot(currentFolder);
			final Folder folder = isRoot ? fileSystem.rootFolder() : folder(currentFolder.getName());
			folders.push(folder);
			currentFolder = currentFolder.getParentFile();
		}
		final List<Folder> relativeFolders = new ArrayList<>();
		while (!folders.isEmpty())
		{
			relativeFolders.add(folders.pop());
		}
		return relativeFolders;
	}

	@NotNull
	public static final Folder Current = new Folder(CurrentFolderName)
	{
		public boolean isCurrent()
		{
			return true;
		}
	};

	@NotNull
	public static final Folder Parent = new Folder(ParentFolderName)
	{
		public boolean isParent()
		{
			return true;
		}
	};

	@NotNull
	public static OrdinaryFolder Ordinary(final @NotNull String name)
	{
		return new OrdinaryFolder(name, false);
	}

	public static boolean isReservedFolderName(final @NotNull String name)
	{
		return name.equals(CurrentFolderName) || name.equals(ParentFolderName);
	}

	private final String name;

	protected Folder(final @NotNull String name)
	{
		this.name = name;
	}

	@NotNull
	public String toString()
	{
		return name;
	}

	public boolean equals(final @Nullable Object o)
	{
		if (this == o)
		{
			return true;
		}
		if (o == null || getClass() != o.getClass())
		{
			return false;
		}

		final Folder folder = (Folder) o;
		return name.equals(folder.name);
	}

	public int hashCode()
	{
		return name.hashCode();
	}

	public boolean isCurrent()
	{
		return false;
	}

	public boolean isParent()
	{
		return false;
	}

	public boolean isOrdinary()
	{
		return false;
	}

	public boolean isRoot()
	{
		return false;
	}

	public boolean isNamed(final @NotNull String thatName)
	{
		return name.equals(thatName);
	}

	public boolean isNotNamed(final @NotNull String thatName)
	{
		return !isNamed(thatName);
	}

	public void serialise(final @NotNull Writer writer)
	{
		try
		{
			writer.write(name);
		}
		catch (IOException e)
		{
			throw new RuntimeException(e);
		}
	}

	public boolean isOrdinaryAndPrefixedWithPeriod()
	{
		return isOrdinary() && name.startsWith(".");
	}

	public static final class OrdinaryFolder extends Folder
	{
		private final boolean isRoot;

		public OrdinaryFolder(final @NotNull String name, final boolean isRoot)
		{
			super(name);
			this.isRoot = isRoot;
			if (isReservedFolderName(name))
			{
				throw new IllegalArgumentException(format(UK, "An OrdinaryFolder can not be named with a reserved folder name %1$s", name));
			}
			if (!isRoot && name.isEmpty())
			{
				throw new IllegalArgumentException("name can not be empty for a folder; Unix root folders are called that, but that's silly");
			}
		}

		public boolean isOrdinary()
		{
			return true;
		}

		public boolean isRoot()
		{
			return isRoot;
		}

		public boolean equals(final @Nullable Object o)
		{
			if (this == o)
			{
				return true;
			}
			if (o == null || getClass() != o.getClass())
			{
				return false;
			}
			if (!super.equals(o))
			{
				return false;
			}

			final OrdinaryFolder that = (OrdinaryFolder) o;
			return isRoot == that.isRoot;
		}

		public int hashCode()
		{
			int result = super.hashCode();
			result = 31 * result + (isRoot ? 1 : 0);
			return result;
		}
	}
}
